#include "thread.h"

ponyThread* currentThread = NULL;

list* runQueue = NULL;

int firstRun = 1;

//#define DEBUG

#ifdef DEBUG
        #define DBG(fmt, args...) printf("%s:%d "fmt,__FILE__,__LINE__,args)
    #else
        #define DBG(fmt, args...)
#endif

void thread_init()
{
  DBG("%s","DEBUG : initialisation de la lib\n");
  ponyThread* mainThread = createPonyThread();
  mainThread->returnValue = NULL;
  getcontext(&(mainThread->context));
  runQueue = addElementToList((void*)mainThread, runQueue);
  DBG("DEBUG : Le thread courant est la fonction main : %p\n", mainThread);
  currentThread = mainThread;
}

void thread_exit(void *retval)
{
  DBG("DEBUG : Fin de la fonction du thread %d\n", ((ponyThread*)thread_self())->id);
   // Le thread se termine, on le signal comme tel
  currentThread->isFinished = 1;
  // On sauvegarde la valeur de retour
  currentThread->returnValue = retval;
  // L'appel à thread_yield() va entrainer le déplacement du thread vers la endQueue
  thread_yield();
}

// Renvoit le thread courant
thread_t thread_self()
{
  if (firstRun)
    {
      thread_init();
      firstRun = 0;
    }
  return (void*) currentThread;
}

void thread_start(void* (*func)(void*), void* arg) 
{
  DBG("DEBUG : Début de la fonction du thread %d\n", ((ponyThread*)thread_self())->id);
  func(arg);
}

int thread_create(thread_t *newthread, void *(*func)(void*), void *funcarg)
{
  *newthread = createPonyThread();
  ponyThread* thread = (ponyThread*) *newthread;
  DBG("DEBUG : Création d'un thread id : %d pointeur =%p\n", thread->id, thread);
  // on recupère le contexte courant
  if (getcontext(&(thread->context)) != 0)
    {
      // en cas d'erreur on renvoit -1, c'est à l'utilisateur de gérer 
      // l'effet de l'erreur sur son programme
      printf("ERREUR : Impossible de sauvegarder le contexte actuel\n");
      return -1;
    }
  else
    {
      // Si le contexte est correctement sauvegardé, on initialise le contexte du nouveau thread
      thread->returnValue = NULL; // la valeur de retour sera renseignée à la fin du thread
      thread->context.uc_link = NULL; // Le thread n'a pas de successeur
      thread->context.uc_stack.ss_sp = malloc( STACK_SIZE ); // Alloue la mémoire de la pile
      thread->context.uc_stack.ss_size = STACK_SIZE; // Renseigne la taille de la pile
      thread->context.uc_stack.ss_flags = 0; // Les flags ne nous intéressent pas

      // Ajout du thread à la liste des threads en cours
      runQueue = addElementToList((void*) thread, runQueue); 

      DBG("%s","DEBUG : makecontext sur un nouveau thread\n");
      // Met en place le contexte du thread et lie la fonction thread_start 
      // TODO : Gérer le cas ou la fonction ne prend pas d'arguments
      makecontext(&(thread->context), (void*)thread_start, 2, func, funcarg);
    }
  return 0;
}

int thread_yield()
{
  // on récupère le thread courant qui est aussi le thread en tête de la runQueue 
  ponyThread* oldThread = NULL;
  ponyThread* newThread = NULL;

  // si le bibliothèque n'est pas initialisée, on l'initialise
  if ( firstRun == 1)
    {
      thread_init();
      firstRun = 0;
    }

  oldThread = ((ponyThread*) runQueue->data);

  if (oldThread->isFinished)
    {
      // Si le thread est terminé
      // on le supprime de la runQueue
      DBG("DEBUG : retire %d de runQueue\n", (((ponyThread*) runQueue->data)->id));
      runQueue = removeElementList(runQueue);
      // Le nouveau thread est le thread suivant dans la liste
      DBG("DEBUG : le nouveau thread est : %d\n", ((ponyThread*) runQueue->data)->id);
      newThread = (ponyThread*)runQueue->data;
    }
  else
    {
      // On appel l'ordonanceur
      DBG("DEBUG : supprime %d de runQueue\n", (((ponyThread*) runQueue->data)->id));
      runQueue = removeElementList(runQueue); // on enlève l'ancien thread
      DBG("DEBUG : place %d à la fin de runQueue\n", oldThread->id);
      runQueue = addElementToListLast(oldThread, runQueue); // on le remet en fin de liste
      // le nouveau thread est donc le thread suivant dans la liste
     
      newThread = ((ponyThread*)runQueue->data);
      DBG("DEBUG : le nouveau thread est %d\n", newThread->id);
    }
  // On dispose maintenant du nouveau thread à exécuter 
  // On sauvegarde le contexte actuel dans oldThread et on charge le contexte
  // du nouveau thread.
  //  DBG("DEBUG : currentThread devient : %d\n", newThread->id);
  currentThread = newThread;
  // on échange les contextes
  DBG("DEBUG : passage du thread %d au thread %d\n", 
	 oldThread->id, currentThread->id);
	 
  int err = swapcontext(&(oldThread->context), &(currentThread->context));
  
  if (err == -1)
    {
      printf("ERREUR : Impossible de changer de contexte dans thread_yield() vers le thread principal\n");
      printf("ERREUR : Le thread courant était : %d\n", ((ponyThread*)thread_self())->id);
      return -1;
    }
  return 0;
}

int thread_join(thread_t thread, void **retval)
{
  ponyThread* joinedThread = (ponyThread*) thread;
  assert(joinedThread->isWaiting != 1 && "Only one join per thread is allowed !");
  DBG("DEBUG : Attente de la fin du thread %d\n", joinedThread->id);
  // On incrémente le nombre de thread en attente de la terminaison du thread
  joinedThread->isWaiting = 1;
  // Tant que le thread n'est pas finit, on repasse la main à un autre thread
  while(!joinedThread->isFinished)
    {
      DBG("DEBUG : Le thread %d n'est toujours pas finit, on passe la main\n", joinedThread->id);
      thread_yield();
    }
  DBG("DEBUG : Le thread %d est terminé, on récupère sa valeur de retour\n", joinedThread->id);
  // Si retval est NULL l'utilisateur n'est pas intéréssé par la valeur de retour
  if (retval != NULL)
    *retval = joinedThread->returnValue;
  freePoneyThread(joinedThread);
  return 0;
}

ponyThread* createPonyThread()
{
  static int id = 0;
  ponyThread* p = malloc(sizeof(ponyThread));
  p->id = id++;
  p->isFinished = 0; 
  p->isWaiting = 0;
  return p;
}

void freePoneyThread(ponyThread* p)
{
  free(p->context.uc_stack.ss_sp);
  free(p);
}
